/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.skstudio.network;

import com.skstudio.report.ReportBase;
import com.skstudio.report.ReportProcess;
import com.skstudio.report.ReportThreads;
import com.skstudio.report.ReportThreads.ThreadClass;
import com.skstudio.report.SpyData;
import com.skstudio.reportview.ReporterBattery;
import com.skstudio.reportview.ReporterCellularThrput;
import com.skstudio.reportview.ReporterCoreSpeed;
import com.skstudio.reportview.ReporterCores;
import com.skstudio.reportview.ReporterMem;
import com.skstudio.reportview.ReporterNetworkThrput;
import com.skstudio.reportview.ReporterProcess;
import com.skstudio.reportview.ReporterProcessMemory;
import com.skstudio.reportview.ReporterProcessThread;
import com.skstudio.reportview.ReporterSysCpu;
import com.skstudio.reportview.ReporterWifiConn;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import org.apache.commons.io.FileUtils;
import taskspydelux.uiRootPanel;
/**
 *
 * @author skstudio
 */
public class TaskSpyClient extends Thread {

    final SpyData mSpyData = new SpyData();
    static final Logger console = Logger.getLogger(TaskSpyClient.class.getName());

    public SpyData getSpyData() {
        return mSpyData;
    }
    boolean mKeepRun = true;
    boolean mLoad = false;

    public void shutdown() {
        mKeepRun = false;
        while (isAlive()) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                Logger.getLogger(TaskSpyClient.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        mSpyData.resetAll();
        console.log(Level.INFO, "TaskSpyClient thread exited");
    }
    public HashMap<Long, String> mNameMap = new HashMap<Long, String>();

    protected void receive() {
        long pid = 0;
        long bytesTotal = 0;
        mSpyData.resetAll();
        mNameMap.clear();

        try {
            mSpyData.beginWrite();
            Socket clientSocket = new Socket(mIpAddr, 6543);

            //2. get Input and Output streams
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
            DataInputStream dis = new DataInputStream(clientSocket.getInputStream());

            for (; mKeepRun;) {
                out.println("Get");

                byte[] dataSegment = readData(dis);
                int cbReceived = dataSegment.length;
                synchronized (this) {
                    mSpyData.segmentWrite(dataSegment, cbReceived);
                }
                bytesTotal += cbReceived;
                //console.log(Level.INFO, String.format("received %d", cbReceived));

                synchronized (mSpyData) {
                    mSpyData.onParseResult(dataSegment, dataSegment.length);
                }
                ReportProcess reProc = mSpyData.getReportProcess();

                synchronized (mSpyData) {
                    if (pid != reProc.mPid) {
                        mSpyData.resetAll();

                        SwingUtilities.invokeLater(new Runnable() {

                            public void run() {
                                mRoot.resetUI();
                            }
                        });

                        mSpyData.onParseResult(dataSegment, dataSegment.length);
                    }
                }
                pid = reProc.mPid;

                //check name
                {
                    String param = null;
                    if (reProc.mProcName == null) {
                        param = String.format("NAME P %d", reProc.mPid);
                        out.println(param);

                        dataSegment = readData(dis);
                        synchronized (mSpyData) {
                            reProc.mProcName = new String(dataSegment, "UTF-8");
                        }

                        //console.log(Level.INFO, String.format("Found process name = %s", reProc.mProcName));
                    }
                    ReportThreads reThreads = mSpyData.getReportThreads();
                    for (int ii = 0; ii < reThreads.mDataList.size(); ii++) {
                        ThreadClass qdc = reThreads.mDataList.get(ii);
                        if (reThreads.getThreadName(qdc.getTid()) == null) {
                            param = String.format("NAME T %d,%d", reProc.mPid, qdc.getTid());
                            out.println(param);

                            dataSegment = readData(dis);
                            String tname = dataSegment[0] == 0 ? null : new String(dataSegment, "UTF-8");
                            if (tname == null || tname.isEmpty() || tname.length() == 0) {
                                tname = "__noname__";
                            }
                            tname = tname.trim();
                            synchronized (mSpyData) {
                                reThreads.setThreadName(qdc.getTid(), tname);
                            }

                            synchronized (this) {
                                mNameMap.put(qdc.getTid(), tname);
                            }

                            //console.log(Level.INFO, String.format("-- thread (%d) name = %s", qdc.getTid(), tname));
                        }
                    }
                }

                console.log(Level.INFO, String.format("received %d with %d threads", cbReceived, mSpyData.getReportThreads().mDataList.size()));

                //update GUI
                SwingUtilities.invokeLater(new Runnable() {

                    public void run() {
                        mRoot.repaint();
                    }
                });
            }

        } catch (final Exception ex) {
            console.log(Level.SEVERE, null, ex);
            SwingUtilities.invokeLater(new Runnable() {

                public void run() {
                    mRoot.reportException(ex);
                }
            });
        }
        mSpyData.endWrite();
        console.log(Level.INFO, String.format("total received %d bytes %d records", bytesTotal, ReportBase.mRecordCount));

        try {
            File file = new File("taskspy.log.tname");
            FileOutputStream f = new FileOutputStream(file, false);
            ObjectOutputStream s = new ObjectOutputStream(f);
            s.writeObject(mNameMap);
            s.close();
        } catch (Exception ex) {
        }
    }

    public void save(File target) {
        try {
            //1. copy the record file
            int count = ReportBase.mRecordCount;
            synchronized (this) {
                FileUtils.copyFile(new File("taskspy.log"), target);
            }
            //update record count on target
            RandomAccessFile raf = new RandomAccessFile(target, "rwd");
            raf.seek(SpyData.cFileSync.length);
            byte[] numSegment = ByteBuffer.allocate(4).putInt(count).array();
            raf.write(numSegment);
            raf.close();

            //2. make threads' name file
            File file = new File(target.getPath() + ".tname");
            FileOutputStream f = new FileOutputStream(file, false);
            ObjectOutputStream s = new ObjectOutputStream(f);
            synchronized (this) {
                s.writeObject(mNameMap);
            }
            s.close();
        } catch (Exception ex) {
        }
    }
    
    protected void load() {
        long pid = 0;
        long bytesTotal = 0;
        mSpyData.resetAll();
        mNameMap.clear();

        try {
            File file = new File(mIpAddr + ".tname");
            FileInputStream f = new FileInputStream(file);
            ObjectInputStream s = new ObjectInputStream(f);
            mNameMap = (HashMap<Long, String>) s.readObject();
            s.close();

            FileInputStream fis = new FileInputStream(mIpAddr);
            int recordCount = mSpyData.beginRead(fis);

            for (int kk = 0; kk < recordCount && mKeepRun; kk++) {

                byte[] dataSegment = mSpyData.segmentRead(fis);
                int cbReceived = dataSegment.length;
                bytesTotal += cbReceived;
                //console.log(Level.INFO, String.format("received %d", cbReceived));

                synchronized (mSpyData) {
                    mSpyData.onParseResult(dataSegment, dataSegment.length);
                }
                ReportProcess reProc = mSpyData.getReportProcess();

                synchronized (mSpyData) {
                    if (pid != reProc.mPid) {
                        mSpyData.resetAll();

                        SwingUtilities.invokeLater(new Runnable() {

                            public void run() {
                                mRoot.resetUI();
                            }
                        });

                        mSpyData.onParseResult(dataSegment, dataSegment.length);
                    }
                }
                pid = reProc.mPid;

                //check name
                {
                    ReportThreads reThreads = mSpyData.getReportThreads();
                    for (int ii = 0; ii < reThreads.mDataList.size(); ii++) {
                        ThreadClass qdc = reThreads.mDataList.get(ii);
                        if (reThreads.getThreadName(qdc.getTid()) == null) {
                            String tname = mNameMap.get(qdc.getTid());
                            reThreads.setThreadName(qdc.getTid(), tname);
                            //console.log(Level.INFO, String.format("-- thread (%d) name = %s", qdc.getTid(), tname));
                        }
                    }
                }

                console.log(Level.INFO, String.format("received %d with %d threads", cbReceived, mSpyData.getReportThreads().mDataList.size()));

                final int percentage = (kk + 1) * 100 / recordCount;

                //update GUI
                SwingUtilities.invokeLater(new Runnable() {

                    public void run() {
                        mRoot.repaint();
                        mRoot.updateLoadingUI(percentage, ReportBase.mRecordCount);
                    }
                });
            }

        } catch (final Exception ex) {
            console.log(Level.SEVERE, null, ex);
            SwingUtilities.invokeLater(new Runnable() {

                public void run() {
                    mRoot.reportException(ex);
                }
            });
        }
        console.log(Level.INFO, String.format("total loaded %d bytes %d records", bytesTotal, ReportBase.mRecordCount));
    }

    @Override
    public void run() {
        if (mLoad) {
            load();
        } else {
            receive();
        }
    }

    byte[] readData(DataInputStream dis) throws IOException {
        int cb = 0, total = 0;
        byte[] sgmtSize = new byte[4];
        do {
            cb = dis.read(sgmtSize, total, 4 - total);
            total += cb;
        } while (total < 4);

        ByteBuffer bb = ByteBuffer.wrap(sgmtSize);
        int count = bb.getInt();

        total = 0;
        byte[] sgmtData = new byte[count];
        do {
            cb = dis.read(sgmtData, total, count - total);
            total += cb;
        } while (total < count);
        return sgmtData;
    }
    uiRootPanel mRoot = null;
    ReporterProcessThread mReProcThreads = new ReporterProcessThread();
    ReporterProcess mReProc = new ReporterProcess();
    ReporterProcessMemory mReProcMem = new ReporterProcessMemory();
    ReporterSysCpu mReSys = new ReporterSysCpu();
    ReporterMem mReMem = new ReporterMem();
    ReporterNetworkThrput mReNetThr = new ReporterNetworkThrput();
    ReporterCores mReCores = new ReporterCores();
    ReporterWifiConn mReWifiConn = new ReporterWifiConn();
    ReporterBattery mReBattery = new ReporterBattery();
    ReporterCellularThrput mReCellular = new ReporterCellularThrput();
    ReporterCoreSpeed mReSpeed = new ReporterCoreSpeed();
    String mIpAddr = null;

    public TaskSpyClient(uiRootPanel root, String ipaddr, boolean load) {
        mIpAddr = ipaddr;
        mLoad = load;

        mRoot = root;
        mReProcThreads.setSpyData(mSpyData);
        mReProc.setSpyData(mSpyData);
        mReProcMem.setSpyData(mSpyData);

        mReSys.setSpyData(mSpyData);
        mReMem.setSpyData(mSpyData);
        mReNetThr.setSpyData(mSpyData);
        mReCores.setSpyData(mSpyData);
        mReWifiConn.setSpyData(mSpyData);
        mReBattery.setSpyData(mSpyData);
        mReCellular.setSpyData(mSpyData);
        mReSpeed.setSpyData(mSpyData);
    }

    public ReporterProcessThread getReProcessThread() {
        return mReProcThreads;
    }

    public ReporterProcess getReProcess() {
        return mReProc;
    }

    public ReporterProcessMemory getReProcessMem() {
        return mReProcMem;
    }

    public ReporterSysCpu getReSysCpu() {
        return mReSys;
    }

    public ReporterMem getReSysMem() {
        return mReMem;
    }

    public ReporterNetworkThrput getReNetworkThru() {
        return mReNetThr;
    }

    public ReporterCores getReCores() {
        return mReCores;
    }

    public ReporterWifiConn getReWifi() {
        return mReWifiConn;
    }

    public ReporterBattery getReBattery() {
        return mReBattery;
    }

    public ReporterCellularThrput getReCellularThru() {
        return mReCellular;
    }

    public ReporterCoreSpeed getReCoreSpeed() {
        return mReSpeed;
    }
}
